from typing import Dict, Any, List, Optional
from datetime import datetime, timedelta
from bson import ObjectId
from pymongo.collection import Collection
import time
import threading
from utils.logger import get_logger
from utils.exceptions import (
    MonitoringException,
    AlertException,
    ValidationException
)

logger = get_logger(__name__)

class BlockchainMonitoringNetwork:
    """区块链监控网络服务 (BMN)
    
    负责:
    1. 多链性能监控
    2. 系统健康检查
    3. 告警管理
    4. 性能指标收集
    5. 实时监控仪表板
    """
    
    def __init__(self, mongo_db):
        self.db = mongo_db
        self.metrics_collection: Collection = mongo_db.monitoring_metrics
        self.alerts_collection: Collection = mongo_db.monitoring_alerts
        self.health_collection: Collection = mongo_db.chain_health
        self.performance_collection: Collection = mongo_db.performance_metrics
        
        # 监控配置
        self.monitoring_interval = 10  # 秒
        self.alert_thresholds = {
            'tps_low': 25000,
            'error_rate_high': 0.05,
            'response_time_high': 5.0,  # 秒
            'block_time_high': 30.0,  # 秒
            'connection_failure_rate': 0.1
        }
        
        # 监控状态
        self.is_monitoring = False
        self.monitoring_thread = None
        
        # 创建索引
        self._create_indexes()
    
    def _create_indexes(self):
        """创建数据库索引"""
        try:
            # 指标集合索引
            self.metrics_collection.create_index([("chain_id", 1), ("timestamp", -1)])
            self.metrics_collection.create_index("metric_type")
            self.metrics_collection.create_index("timestamp")
            
            # 告警集合索引
            self.alerts_collection.create_index("chain_id")
            self.alerts_collection.create_index("alert_type")
            self.alerts_collection.create_index("status")
            self.alerts_collection.create_index("created_at")
            
            # 健康状态集合索引
            self.health_collection.create_index("chain_id", unique=True)
            self.health_collection.create_index("last_updated")
            
            # 性能指标集合索引
            self.performance_collection.create_index([("chain_id", 1), ("timestamp", -1)])
            self.performance_collection.create_index("operation_type")
            
        except Exception as e:
            logger.warning(f"Failed to create indexes: {str(e)}")
    
    def start_monitoring(self):
        """启动监控"""
        if self.is_monitoring:
            logger.warning("Monitoring is already running")
            return
        
        self.is_monitoring = True
        self.monitoring_thread = threading.Thread(target=self._monitoring_loop, daemon=True)
        self.monitoring_thread.start()
        
        logger.info("Blockchain monitoring started")
    
    def stop_monitoring(self):
        """停止监控"""
        self.is_monitoring = False
        if self.monitoring_thread:
            self.monitoring_thread.join(timeout=5)
        
        logger.info("Blockchain monitoring stopped")
    
    def _monitoring_loop(self):
        """监控循环"""
        while self.is_monitoring:
            try:
                self._collect_metrics()
                self._check_health()
                self._evaluate_alerts()
                time.sleep(self.monitoring_interval)
            except Exception as e:
                logger.error(f"Error in monitoring loop: {str(e)}")
                time.sleep(self.monitoring_interval)
    
    def _collect_metrics(self):
        """收集监控指标"""
        try:
            # 这里应该从各个区块链适配器收集实际指标
            # 目前使用模拟数据
            chains = ['fisco_bcos', 'chainmaker', 'jdchain', 'xuperchain']
            
            for chain_id in chains:
                metrics = self._get_chain_metrics(chain_id)
                self._store_metrics(chain_id, metrics)
                
        except Exception as e:
            logger.error(f"Failed to collect metrics: {str(e)}")
    
    def _get_chain_metrics(self, chain_id: str) -> Dict[str, Any]:
        """获取链的监控指标"""
        # 这里应该调用实际的区块链适配器获取指标
        # 目前返回模拟数据
        import random
        
        return {
            'tps': random.randint(20000, 35000),
            'block_height': random.randint(100000, 200000),
            'block_time': random.uniform(1.0, 5.0),
            'peer_count': random.randint(4, 10),
            'pending_transactions': random.randint(0, 1000),
            'error_rate': random.uniform(0.001, 0.01),
            'response_time': random.uniform(0.1, 2.0),
            'cpu_usage': random.uniform(20.0, 80.0),
            'memory_usage': random.uniform(30.0, 70.0),
            'disk_usage': random.uniform(40.0, 90.0),
            'network_in': random.uniform(100.0, 1000.0),
            'network_out': random.uniform(100.0, 1000.0)
        }
    
    def _store_metrics(self, chain_id: str, metrics: Dict[str, Any]):
        """存储监控指标"""
        try:
            metric_doc = {
                'chain_id': chain_id,
                'timestamp': datetime.utcnow(),
                'metrics': metrics,
                'metric_type': 'system'
            }
            
            self.metrics_collection.insert_one(metric_doc)
            
        except Exception as e:
            logger.error(f"Failed to store metrics for {chain_id}: {str(e)}")
    
    def _check_health(self):
        """检查链健康状态"""
        try:
            chains = ['fisco_bcos', 'chainmaker', 'jdchain', 'xuperchain']
            
            for chain_id in chains:
                health_status = self._get_chain_health(chain_id)
                self._update_health_status(chain_id, health_status)
                
        except Exception as e:
            logger.error(f"Failed to check health: {str(e)}")
    
    def _get_chain_health(self, chain_id: str) -> Dict[str, Any]:
        """获取链健康状态"""
        # 这里应该调用实际的区块链适配器检查健康状态
        import random
        
        is_healthy = random.choice([True, True, True, False])  # 75%概率健康
        
        return {
            'is_healthy': is_healthy,
            'is_syncing': random.choice([True, False]),
            'peers_connected': random.randint(3, 8),
            'latest_block_time': datetime.utcnow() - timedelta(seconds=random.randint(1, 10)),
            'connection_status': 'connected' if is_healthy else 'disconnected',
            'sync_progress': random.uniform(95.0, 100.0) if is_healthy else random.uniform(0.0, 50.0)
        }
    
    def _update_health_status(self, chain_id: str, health_status: Dict[str, Any]):
        """更新健康状态"""
        try:
            health_doc = {
                'chain_id': chain_id,
                'health_status': health_status,
                'last_updated': datetime.utcnow()
            }
            
            self.health_collection.update_one(
                {'chain_id': chain_id},
                {'$set': health_doc},
                upsert=True
            )
            
        except Exception as e:
            logger.error(f"Failed to update health status for {chain_id}: {str(e)}")
    
    def _evaluate_alerts(self):
        """评估告警条件"""
        try:
            # 获取最近的指标数据
            recent_time = datetime.utcnow() - timedelta(minutes=5)
            
            chains = ['fisco_bcos', 'chainmaker', 'jdchain', 'xuperchain']
            
            for chain_id in chains:
                metrics = list(self.metrics_collection.find({
                    'chain_id': chain_id,
                    'timestamp': {'$gte': recent_time}
                }).sort('timestamp', -1).limit(1))
                
                if metrics:
                    latest_metrics = metrics[0]['metrics']
                    self._check_alert_conditions(chain_id, latest_metrics)
                    
        except Exception as e:
            logger.error(f"Failed to evaluate alerts: {str(e)}")
    
    def _check_alert_conditions(self, chain_id: str, metrics: Dict[str, Any]):
        """检查告警条件"""
        try:
            # 检查TPS过低
            if metrics.get('tps', 0) < self.alert_thresholds['tps_low']:
                self._create_alert(
                    chain_id=chain_id,
                    alert_type='tps_low',
                    severity='warning',
                    message=f"TPS is below threshold: {metrics['tps']} < {self.alert_thresholds['tps_low']}",
                    metrics=metrics
                )
            
            # 检查错误率过高
            if metrics.get('error_rate', 0) > self.alert_thresholds['error_rate_high']:
                self._create_alert(
                    chain_id=chain_id,
                    alert_type='error_rate_high',
                    severity='critical',
                    message=f"Error rate is above threshold: {metrics['error_rate']} > {self.alert_thresholds['error_rate_high']}",
                    metrics=metrics
                )
            
            # 检查响应时间过长
            if metrics.get('response_time', 0) > self.alert_thresholds['response_time_high']:
                self._create_alert(
                    chain_id=chain_id,
                    alert_type='response_time_high',
                    severity='warning',
                    message=f"Response time is above threshold: {metrics['response_time']}s > {self.alert_thresholds['response_time_high']}s",
                    metrics=metrics
                )
            
            # 检查区块时间过长
            if metrics.get('block_time', 0) > self.alert_thresholds['block_time_high']:
                self._create_alert(
                    chain_id=chain_id,
                    alert_type='block_time_high',
                    severity='warning',
                    message=f"Block time is above threshold: {metrics['block_time']}s > {self.alert_thresholds['block_time_high']}s",
                    metrics=metrics
                )
                
        except Exception as e:
            logger.error(f"Failed to check alert conditions for {chain_id}: {str(e)}")
    
    def _create_alert(self, chain_id: str, alert_type: str, severity: str, message: str, metrics: Dict[str, Any]):
        """创建告警"""
        try:
            # 检查是否已存在相同的活跃告警
            existing_alert = self.alerts_collection.find_one({
                'chain_id': chain_id,
                'alert_type': alert_type,
                'status': 'active'
            })
            
            if existing_alert:
                # 更新现有告警的最后发生时间
                self.alerts_collection.update_one(
                    {'_id': existing_alert['_id']},
                    {
                        '$set': {
                            'last_occurred': datetime.utcnow(),
                            'occurrence_count': existing_alert.get('occurrence_count', 1) + 1,
                            'latest_metrics': metrics
                        }
                    }
                )
                return
            
            # 创建新告警
            alert_doc = {
                'chain_id': chain_id,
                'alert_type': alert_type,
                'severity': severity,
                'message': message,
                'status': 'active',
                'created_at': datetime.utcnow(),
                'last_occurred': datetime.utcnow(),
                'occurrence_count': 1,
                'acknowledged': False,
                'acknowledged_by': None,
                'acknowledged_at': None,
                'resolved': False,
                'resolved_by': None,
                'resolved_at': None,
                'metrics': metrics,
                'latest_metrics': metrics
            }
            
            result = self.alerts_collection.insert_one(alert_doc)
            alert_id = str(result.inserted_id)
            
            logger.warning(f"Alert created: {alert_id} - {chain_id} - {alert_type} - {message}")
            
        except Exception as e:
            logger.error(f"Failed to create alert: {str(e)}")
    
    def record_operation(self, chain_id: str, operation: str, success: bool, response_time: float = None):
        """记录操作性能指标"""
        try:
            performance_doc = {
                'chain_id': chain_id,
                'operation_type': operation,
                'success': success,
                'response_time': response_time,
                'timestamp': datetime.utcnow()
            }
            
            self.performance_collection.insert_one(performance_doc)
            
        except Exception as e:
            logger.error(f"Failed to record operation performance: {str(e)}")
    
    def get_system_metrics(self) -> Dict[str, Any]:
        """获取系统性能指标"""
        try:
            # 获取最近的指标
            recent_time = datetime.utcnow() - timedelta(minutes=5)
            
            # 聚合各链的指标
            pipeline = [
                {'$match': {'timestamp': {'$gte': recent_time}}},
                {
                    '$group': {
                        '_id': '$chain_id',
                        'avg_tps': {'$avg': '$metrics.tps'},
                        'avg_response_time': {'$avg': '$metrics.response_time'},
                        'avg_error_rate': {'$avg': '$metrics.error_rate'},
                        'latest_block_height': {'$max': '$metrics.block_height'},
                        'avg_cpu_usage': {'$avg': '$metrics.cpu_usage'},
                        'avg_memory_usage': {'$avg': '$metrics.memory_usage'}
                    }
                }
            ]
            
            chain_metrics = list(self.metrics_collection.aggregate(pipeline))
            
            # 计算总体指标
            total_tps = sum(metric.get('avg_tps', 0) for metric in chain_metrics)
            avg_response_time = sum(metric.get('avg_response_time', 0) for metric in chain_metrics) / len(chain_metrics) if chain_metrics else 0
            avg_error_rate = sum(metric.get('avg_error_rate', 0) for metric in chain_metrics) / len(chain_metrics) if chain_metrics else 0
            
            # 获取活跃告警数量
            active_alerts = self.alerts_collection.count_documents({'status': 'active'})
            
            # 获取健康链数量
            healthy_chains = self.health_collection.count_documents({'health_status.is_healthy': True})
            total_chains = self.health_collection.count_documents({})
            
            return {
                'total_tps': total_tps,
                'average_response_time': avg_response_time,
                'average_error_rate': avg_error_rate,
                'active_alerts': active_alerts,
                'healthy_chains': healthy_chains,
                'total_chains': total_chains,
                'chain_metrics': {metric['_id']: metric for metric in chain_metrics},
                'timestamp': datetime.utcnow().isoformat()
            }
            
        except Exception as e:
            logger.error(f"Failed to get system metrics: {str(e)}")
            raise MonitoringException(f"Failed to retrieve system metrics: {str(e)}")
    
    def get_chain_metrics(self, chain_id: str, hours: int = 24) -> Dict[str, Any]:
        """获取特定链的指标历史"""
        try:
            start_time = datetime.utcnow() - timedelta(hours=hours)
            
            cursor = self.metrics_collection.find({
                'chain_id': chain_id,
                'timestamp': {'$gte': start_time}
            }).sort('timestamp', 1)
            
            metrics_history = list(cursor)
            
            # 转换时间戳为字符串
            for metric in metrics_history:
                metric['_id'] = str(metric['_id'])
                metric['timestamp'] = metric['timestamp'].isoformat()
            
            return {
                'chain_id': chain_id,
                'metrics_history': metrics_history,
                'total_records': len(metrics_history)
            }
            
        except Exception as e:
            logger.error(f"Failed to get chain metrics for {chain_id}: {str(e)}")
            raise MonitoringException(f"Failed to retrieve chain metrics: {str(e)}")
    
    def get_alerts(self, chain_id: str = None, status: str = None, severity: str = None, 
                  page: int = 1, page_size: int = 20) -> Dict[str, Any]:
        """获取告警列表"""
        try:
            query = {}
            
            if chain_id:
                query['chain_id'] = chain_id
            if status:
                query['status'] = status
            if severity:
                query['severity'] = severity
            
            skip = (page - 1) * page_size
            
            cursor = self.alerts_collection.find(query).sort('created_at', -1).skip(skip).limit(page_size)
            alerts = list(cursor)
            
            # 转换ObjectId为字符串
            for alert in alerts:
                alert['_id'] = str(alert['_id'])
                alert['created_at'] = alert['created_at'].isoformat()
                alert['last_occurred'] = alert['last_occurred'].isoformat()
                if alert.get('acknowledged_at'):
                    alert['acknowledged_at'] = alert['acknowledged_at'].isoformat()
                if alert.get('resolved_at'):
                    alert['resolved_at'] = alert['resolved_at'].isoformat()
            
            total_count = self.alerts_collection.count_documents(query)
            
            return {
                'alerts': alerts,
                'total_count': total_count,
                'page': page,
                'page_size': page_size,
                'total_pages': (total_count + page_size - 1) // page_size
            }
            
        except Exception as e:
            logger.error(f"Failed to get alerts: {str(e)}")
            raise MonitoringException(f"Failed to retrieve alerts: {str(e)}")
    
    def acknowledge_alert(self, alert_id: str, acknowledged_by: str) -> bool:
        """确认告警"""
        try:
            result = self.alerts_collection.update_one(
                {'_id': ObjectId(alert_id)},
                {
                    '$set': {
                        'acknowledged': True,
                        'acknowledged_by': acknowledged_by,
                        'acknowledged_at': datetime.utcnow()
                    }
                }
            )
            
            if result.matched_count == 0:
                raise AlertException("Alert not found")
            
            logger.info(f"Alert acknowledged: {alert_id} by {acknowledged_by}")
            return True
            
        except Exception as e:
            if isinstance(e, AlertException):
                raise
            logger.error(f"Failed to acknowledge alert: {str(e)}")
            raise AlertException(f"Failed to acknowledge alert: {str(e)}")
    
    def resolve_alert(self, alert_id: str, resolved_by: str, resolution_notes: str = None) -> bool:
        """解决告警"""
        try:
            update_data = {
                'status': 'resolved',
                'resolved': True,
                'resolved_by': resolved_by,
                'resolved_at': datetime.utcnow()
            }
            
            if resolution_notes:
                update_data['resolution_notes'] = resolution_notes
            
            result = self.alerts_collection.update_one(
                {'_id': ObjectId(alert_id)},
                {'$set': update_data}
            )
            
            if result.matched_count == 0:
                raise AlertException("Alert not found")
            
            logger.info(f"Alert resolved: {alert_id} by {resolved_by}")
            return True
            
        except Exception as e:
            if isinstance(e, AlertException):
                raise
            logger.error(f"Failed to resolve alert: {str(e)}")
            raise AlertException(f"Failed to resolve alert: {str(e)}")
    
    def get_health_status(self, chain_id: str = None) -> Dict[str, Any]:
        """获取健康状态"""
        try:
            if chain_id:
                health = self.health_collection.find_one({'chain_id': chain_id})
                if not health:
                    raise MonitoringException(f"No health data found for chain {chain_id}")
                
                health['_id'] = str(health['_id'])
                health['last_updated'] = health['last_updated'].isoformat()
                return health
            else:
                # 获取所有链的健康状态
                cursor = self.health_collection.find({})
                health_data = list(cursor)
                
                for health in health_data:
                    health['_id'] = str(health['_id'])
                    health['last_updated'] = health['last_updated'].isoformat()
                
                return {
                    'chains': health_data,
                    'total_chains': len(health_data),
                    'healthy_chains': sum(1 for h in health_data if h['health_status']['is_healthy'])
                }
                
        except Exception as e:
            if isinstance(e, MonitoringException):
                raise
            logger.error(f"Failed to get health status: {str(e)}")
            raise MonitoringException(f"Failed to retrieve health status: {str(e)}")